package com.yutian.commandmysql;//import com.mysql.jdbc.Connection;
//import com.mysql.jdbc.*;

import java.sql.DriverManager;
import java.sql.*;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcUtils {
    private static final String JDBC_URL_SUFFIX = "?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai";

    static {
        try {
            // MySQL 8.0 以上版本 - JDBC 驱动名及数据库 URL
            //static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";

            // MySQL 8.0 以下版本 - JDBC 驱动名及数据库 URL
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /*
    *
    * 建立连接
    * */
    private static Connection getConnection(DbConfig dbConfig, String database) {
        String url = "jdbc:mysql://"+dbConfig.getIp()+":"+dbConfig.getPort()+"/"+database+JDBC_URL_SUFFIX;
        String username = dbConfig.getUsername();
        String password = dbConfig.getPassword();

        Connection con = null;

        try {
            con = DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return con;
    }

    private static void close(ResultSet rs, Statement stm, Connection con) {

        try {
            if (rs != null) {
                rs.close();
            }

            if (stm != null) {
                stm.close();
            }

            if (con != null) {
                con.close();
            }
        } catch (Exception e) {
            // 吃掉
            e.printStackTrace();
        }
    }

    /**
     * 方法executeQuery
     * 用于产生单个结果集（ResultSet）的语句，例如:被执行最多的SELECT 语句。
     * 这个方法被用来执行 SELECT 语句，但也只能执行查询语句，执行后返回代表查询结果的ResultSet对象。
     * @param dbConfig
     * @param database
     * @param preparedStatementSql
     */
    public static void query(DbConfig dbConfig, String database, String preparedStatementSql, Object[] params) {
        Connection con = getConnection(dbConfig, database);

        PreparedStatement pres = null;
        ResultSet rs = null;
        try {
            pres = con.prepareStatement(preparedStatementSql);
            // 使用setObject可以自动识别SQL中的数据类型
            for(int i=0;i<params.length;i++) {
                // 从1开始
                pres.setObject(i+1, params[i]);
            }
            rs = pres.executeQuery();
            printResultSet(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            close(rs, pres, con);
        }
    }

    /**
     * 方法executeUpdate
     * 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL（数据定义语言）语句，例如 CREATE TABLE 和 DROP TABLE。
     * INSERT、UPDATE 或 DELETE 语句的效果是修改表中零行或多行中的一列或多列。
     * executeUpdate 的返回值是一个整数（int），指示受影响的行数（即更新计数）。
     * 对于 CREATE TABLE 或 DROP TABLE 等不操作行的语句，executeUpdate 的返回值总为零。
     * @param dbConfig
     * @param database
     * @param preparedStatementSql
     * @param params
     * @return 返回影响的行数
     */
    public static int executeUpdate(DbConfig dbConfig, String database, String preparedStatementSql, Object[] params) {
        // String sql = "insert into sell(sell_id, user_name, price) values(?, ?, ?)";
        Connection con = getConnection(dbConfig, database);

        PreparedStatement pres = null;
        ResultSet rs = null;

        try {
            pres = con.prepareStatement(preparedStatementSql);
            // 使用setObject可以自动识别SQL中的数据类型
            for(int i=0;i<params.length;i++) {
                // 从1开始
                pres.setObject(i+1, params[i]);
            }
            int effectRows = pres.executeUpdate();
            System.out.println("该SQL语句影响的记录有" + effectRows + "条");
            return effectRows;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            close(rs, pres, con);
        }
    }

    /*
    * 方法execute：
    * 可用于执行任何SQL语句，返回一个boolean值，表明执行该SQL语句是否返回了ResultSet。
    * 如果执行后第一个结果是ResultSet，则返回true，否则返回false。
    * 但它执行SQL语句时比较麻烦，通常我们没有必要使用execute方法来执行SQL语句，而是使用executeQuery或executeUpdate更适合。
    * 但如果在不清楚SQL语句的类型时则只能使用execute方法来执行该SQL语句了。
    * */
    public static void execute(DbConfig dbConfig, String database, String preparedStatementSql, Object[] params) {
        // String sql = "insert into sell(sell_id, user_name, price) values(?, ?, ?)";
        Connection con = getConnection(dbConfig, database);

        PreparedStatement pres = null;
        ResultSet rs = null;

        try {
            pres = con.prepareStatement(preparedStatementSql);
            // 使用setObject可以自动识别SQL中的数据类型
            for(int i=0;i<params.length;i++) {
                // 从1开始
                pres.setObject(i+1, params[i]);
            }
            boolean hasResultSet = pres.execute();
            //执行SQL,返回boolean值表示是否包含ResultSet
            if (hasResultSet) { //如果执行后有ResultSet结果集
                rs = pres.getResultSet();
                printResultSet(rs);
            } else  {
                System.out.println("该SQL语句影响的记录有" + pres.getUpdateCount() + "条");
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            close(rs, pres, con);
        }
    }

    private static void printResultSet(ResultSet rs) throws SQLException {
        System.out.println("结果为:");
        //获取结果集
        ResultSetMetaData rsmd = rs.getMetaData();
        //ResultSetMetaData是用于分析结果集的元数据接口
        int  columnCount = rsmd.getColumnCount();
        while (rs.next()){//输出ResultSet对象
            // 标题
            for (int i = 0 ; i < columnCount ; i++ ) {
                System.out.print(rsmd.getColumnLabel(i+1) + "     |     ");
            }
            System.out.println("\r\n-----------------------------------------------------");
            // 内容
            for (int i = 0 ; i < columnCount ; i++ ) {
                System.out.print(rs.getString(i + 1) + "     |     ");
            }
            System.out.print("\r\n");
        }
    }

}
